#include "analyzer_common.h"

char buf[500];

sparse_matrix_t *init_smatrix()
{
    sparse_matrix_t* new_malloc = malloc(sizeof(sparse_matrix_t));
    memset(new_malloc, 0, sizeof(sparse_matrix_t));
    return new_malloc;
}

int read_matrix(char *mtx_name, sparse_matrix_t *smatrix)
{
    switch (mtx_name[strlen(mtx_name) - 1])
    {
    case 'x':
        // mtx read
        printf("Reading mtx matrix %s\n", mtx_name);
        mmio_info(&smatrix->m, &smatrix->n, &smatrix->nnz, &smatrix->is_sym, mtx_name);
        smatrix->rowpointer = (sparse_pointer_t *)malloc(sizeof(sparse_pointer_t) * (smatrix->m + 1));
        smatrix->columnindex = (sparse_index_t *)malloc(sizeof(sparse_index_t) * smatrix->nnz);
        smatrix->value = (sparse_value_t *)malloc(sizeof(sparse_value_t) * smatrix->nnz);
        mmio_data_csr(smatrix->rowpointer, smatrix->columnindex, smatrix->value, mtx_name);
        printf("Read mtx done.\n");
        break;
    case 'd':
        // // lid write
        // char buf[100];
        // sprintf(buf, "%s.lid", mtx_name);
        // FILE* lid_file = fopen(buf, "w");
        // fwrite(&m, sizeof(sparse_index_t), 1, lid_file);
        // fwrite(&n, sizeof(sparse_index_t), 1, lid_file);
        // fwrite(&nnz, sizeof(sparse_pointer_t), 1, lid_file);
        // fwrite(rowptr, sizeof(sparse_pointer_t), n+1, lid_file);
        // fwrite(colidx, sizeof(sparse_index_t), nnz, lid_file);
        // fwrite(value, sizeof(sparse_value_t), nnz, lid_file);
        // fclose(lid_file);

        // lid read
        printf("Reading lid matrix %s\n", mtx_name);
        FILE *lid_file = fopen(mtx_name, "r");
        fread(&smatrix->m, sizeof(sparse_index_t), 1, lid_file);
        fread(&smatrix->n, sizeof(sparse_index_t), 1, lid_file);
        fread(&smatrix->nnz, sizeof(sparse_pointer_t), 1, lid_file);
        smatrix->rowpointer = (sparse_pointer_t *)malloc(sizeof(sparse_pointer_t) * (smatrix->m + 1));
        smatrix->columnindex = (sparse_index_t *)malloc(sizeof(sparse_index_t) * smatrix->nnz);
        smatrix->value = (sparse_value_t *)malloc(sizeof(sparse_value_t) * smatrix->nnz);
        fread(smatrix->rowpointer, sizeof(sparse_pointer_t), smatrix->m + 1, lid_file);
        fread(smatrix->columnindex, sizeof(sparse_index_t), smatrix->nnz, lid_file);
        size_t current_file_ptr = ftell(lid_file);
        fseek(lid_file, 0, SEEK_END);
        size_t file_len = ftell(lid_file);
        if(current_file_ptr != file_len){
            fseek(lid_file, current_file_ptr, SEEK_SET);
            fread(smatrix->value, sizeof(sparse_value_t), smatrix->nnz, lid_file);
            printf("Read lid with value done.\n");
        }else{
            // memset(smatrix->value, 0, sizeof(sparse_value_t) * smatrix->nnz);
            for(sparse_pointer_t idx = 0; idx < smatrix->nnz; idx++){
                smatrix->value[idx] = 1;
            }
            printf("Read lid without value done. Set default value = 1.\n");
        }
        fclose(lid_file);

        break;
    }
    printf("(%d, %d) nnz=%lld\n", smatrix->m, smatrix->n, smatrix->nnz);
}

int export_lid(const char *path, sparse_matrix_t *smatrix)
{
    // lid write
    printf("Exporting matrix to %s\n", path);
    
    FILE* lid_file = fopen(path, "w");
    while(!lid_file){
    	printf("fopen(\"w\") failed : %s. Please repair and input any char.\n", path);
	char ch;
	scanf("%c", &ch);
	lid_file = fopen(path, "w");
    }
    printf("Open file successfully. Exporting.\n");
    
    fwrite(&smatrix->m, sizeof(sparse_index_t), 1, lid_file);
    fwrite(&smatrix->n, sizeof(sparse_index_t), 1, lid_file);
    fwrite(&smatrix->nnz, sizeof(sparse_pointer_t), 1, lid_file);
    fwrite(smatrix->rowpointer, sizeof(sparse_pointer_t), smatrix->n+1, lid_file);
    fwrite(smatrix->columnindex, sizeof(sparse_index_t), smatrix->nnz, lid_file);
    fwrite(smatrix->value, sizeof(sparse_value_t), smatrix->nnz, lid_file);
    fclose(lid_file);
}

int export_mtx(const char *path, sparse_matrix_t *smatrix)
{
    printf("Exporting matrix to %s\n", path);

    FILE* output_file = fopen(path, "w");
    
    while(!output_file){
    	printf("fopen(\"w\") failed : %s. Please repair and input any char.\n", path);
	char ch;
	scanf("%c", &ch);
	output_file = fopen(path, "w");
    }
    printf("Open file successfully. Exporting.\n");

    if(smatrix->value){
        fprintf(output_file, "%%%%MatrixMarket matrix coordinate real general\n"FMT_SPARSE_INDEX_T" "FMT_SPARSE_INDEX_T" "FMT_SPARSE_POINTER_T"\n", smatrix->m, smatrix->n, smatrix->nnz);
    }else{
        fprintf(output_file, "%%%%MatrixMarket matrix coordinate pattern general\n"FMT_SPARSE_INDEX_T" "FMT_SPARSE_INDEX_T" "FMT_SPARSE_POINTER_T"\n", smatrix->m, smatrix->n, smatrix->nnz);
    }

    for(sparse_index_t row = 0; row < smatrix->m; row++){
        for(sparse_pointer_t idx = smatrix->rowpointer[row]; idx < smatrix->rowpointer[row+1]; idx++){
            if(smatrix->value){
                fprintf(output_file, ""FMT_SPARSE_INDEX_T" "FMT_SPARSE_INDEX_T" %lg\n", row+1, smatrix->columnindex[idx]+1, smatrix->value[idx]);
            }else{
                fprintf(output_file, ""FMT_SPARSE_INDEX_T" "FMT_SPARSE_INDEX_T"\n", row+1, smatrix->columnindex[idx]+1);
            }
        }
    }
    
    fclose(output_file);
    printf("Export matrix %s done.\n", path);
}

int read_vector(const char *vec_name, sparse_index_t* length, sparse_value_t** value){
    switch (vec_name[strlen(vec_name) - 1])
    {
    case 't':
        printf("Reading dat vector %s\n", vec_name);
        int n;
        FILE* input_vec = fopen(vec_name, "r");
        fread(&n, sizeof(int), 1, input_vec);
        *length = n;
        *value = malloc(sizeof(sparse_value_t) * n);
        fread(*value, sizeof(sparse_value_t), n, input_vec);
        fclose(input_vec);
        printf("Read dat vector done.\n");
        printf("length = %d\n", n);
        break;
    }
}

void analyzer_timer_start(struct timeval* start){
    gettimeofday(start, NULL);
}

double analyzer_timer_stop(struct timeval* start){
    struct timeval end;
    gettimeofday(&end, NULL);
    double time = ((double)end.tv_sec - start->tv_sec) + ((double)end.tv_usec - start->tv_usec) * 1e-6;
    return time;
}

double analyzer_csr_to_bsr(sparse_matrix_t* s, sparse_matrix_t* s_bsr, int block_order){
    if(block_order <= 1){
        return;
    }

    sparse_pointer_t nnz_submtx = 0;
    sparse_index_t n_order = s->m / block_order;

    struct timeval time_start;

    s_bsr->m = n_order;
    s_bsr->n = n_order;
    s_bsr->nnz = s->nnz / block_order / block_order;
    s_bsr->is_sym = 0;
    s_bsr->name = NULL;
    s_bsr->rowpointer = malloc(sizeof(sparse_pointer_t) * (s_bsr->m + 1));
    s_bsr->columnindex = malloc(sizeof(sparse_index_t) * s_bsr->nnz);
    s_bsr->value = malloc(sizeof(sparse_value_t) * s_bsr->nnz * block_order * block_order);

    // printf("0\n");
    analyzer_timer_start(&time_start);
    #pragma omp parallel for
    for(sparse_index_t brow = 0; brow < n_order; brow++){
        s_bsr->rowpointer[brow] = s->rowpointer[brow * block_order] / (block_order * block_order);
    }
    s_bsr->rowpointer[n_order] = s_bsr->nnz;
    // printf("1 : %.2lf s\n", analyzer_timer_stop(&time_start));

    analyzer_timer_start(&time_start);
    #pragma omp parallel for
    for(sparse_index_t brow = 0; brow < n_order; brow++){
        for(sparse_pointer_t bidx = s_bsr->rowpointer[brow]; bidx < s_bsr->rowpointer[brow+1]; bidx++){
            s_bsr->columnindex[bidx] 
                = s->columnindex[s->rowpointer[brow * block_order] + (bidx - s_bsr->rowpointer[brow]) * block_order] / block_order;
        }
    }
    // printf("2 : %.2lf s\n", analyzer_timer_stop(&time_start));


    analyzer_timer_start(&time_start);
    #pragma omp parallel
    {
        int nthreads = omp_get_num_threads();
        int tid = omp_get_thread_num();
        sparse_index_t thread_nrow = (n_order + nthreads - 1) / nthreads;
        sparse_index_t thread_start_row = MIN(thread_nrow * tid, n_order);
        sparse_index_t thread_end_row = MIN(thread_nrow * (tid + 1), n_order);
        
        if(thread_start_row < thread_end_row){
            sparse_pointer_t value_idx = s_bsr->rowpointer[thread_start_row];
            for(sparse_index_t brow = thread_start_row; brow < thread_end_row; brow++){
                for(sparse_pointer_t idx = s->rowpointer[brow * block_order]; idx < s->rowpointer[brow * block_order + 1]; idx+=block_order){
                    sparse_pointer_t inrow_offset = idx - s->rowpointer[brow * block_order];
                    for(int row_in_block = 0; row_in_block < block_order; row_in_block++){
                        sparse_pointer_t row_pointer = s->rowpointer[brow * block_order + row_in_block];
                        for(int col_in_block = 0; col_in_block < block_order; col_in_block++){
                            s_bsr->value[value_idx * block_order * block_order + row_in_block * block_order + col_in_block]
                                = s->value[row_pointer + inrow_offset + col_in_block];
                        }
                    }
                    value_idx++;
                }
            }
        }
    }
    // printf("3 : %.2lf s\n", analyzer_timer_stop(&time_start));

}
